package stud.mpgame.server;

import java.util.logging.Level;
import java.util.logging.Logger;

import org.apache.mina.core.session.IoSession;

import stud.mpgame.net.description.TestCommandDescription;
import stud.mpgame.net.description.TestSnapshotDescription;
import stud.mpgame.net.description.GameWorldMessageDescription;

import stud.mpgame.net.command.TestCommand;

import stud.mpgame.net.userobject.MapInfoUserObject;
import stud.mpgame.network.IMessageReceivedListener;
import stud.mpgame.network.NetworkMonitor;
import stud.mpgame.network.TCPNetworkMonitor;
import stud.mpgame.network.MessageDescription;
import stud.mpgame.network.Message;

import stud.mpgame.network.util.MessageFilter;

import stud.mpgame.server.game.GameCommandDispatcher;
import stud.mpgame.server.game.GameWorld;
import stud.mpgame.server.map.MapData;
import stud.mpgame.server.map.MapDeserializer;
import stud.mpgame.server.preview.PreviewGameManager;
import stud.mpgame.server.test.TestSnapshot;

import stud.mpgame.snapshot.SnapshotManager;

public class Main {

    private static final int SNAPSHOT_MANAGER_SNAPSHOT_LENGTH = 20;
    private static final int SNAPSHOT_MANAGER_INITIAL_POSITION = 10;

    private static final String defaultConfig = "server.properties";

    private final int protocolVersion = 1;

    private long startAppTime;
    private int delay;

    private NetworkMonitor networkMonitor;
    private SnapshotManager snapshotManager;
    private PreviewGameManager previewGameManager;
    private PingAnalyzer pingAnalyzer;

    private MapData mapData;

    private Main() throws Exception {
        this.startAppTime = System.currentTimeMillis();
        this.networkMonitor = new TCPNetworkMonitor(startAppTime, protocolVersion);
    }

    private void initializationServer(String path) throws Exception {
        ServerConfiguration.getInstance().initialization(path);
        this.delay = ServerConfiguration.getInstance().getDelay();
        this.previewGameManager = new PreviewGameManager(networkMonitor);
        this.pingAnalyzer = new PingAnalyzer(networkMonitor);
        this.snapshotManager = new SnapshotManager(delay, SNAPSHOT_MANAGER_SNAPSHOT_LENGTH);
        new GameCommandDispatcher(networkMonitor, snapshotManager);
        this.mapData = MapDeserializer.deserialize(MapInfoUserObject.mapURL);
    }

    public static void main(String[] args) throws Exception {
        Main main = new Main();
        main.initializationServer(args.length > 0 ? args[0] : defaultConfig);
        main.bind();
        main.start();
    }

    private void bind() throws Exception {

        MessageDescription.registerDescription(TestCommandDescription.getInstance());
        MessageDescription.registerDescription(TestSnapshotDescription.getInstance());
        MessageDescription.registerDescription(GameWorldMessageDescription.getInstance());

        MessageFilter messageFilter = new MessageFilter(TestCommandDescription.getInstance());

        networkMonitor.getMessageDispatcher().addMessageReceivedListener(messageFilter,
                new IMessageReceivedListener() {
                    public void messageReceived(Message message) {
                        TestCommand testCommand = (TestCommand) message.getUserObject(TestCommand.class);
                        System.out.println("CMD: " + testCommand.getTime());
                        snapshotManager.addCommand(testCommand);
                    }
                }
        );

        networkMonitor.bind();
    }

    private void start() throws Exception {
    	
    	int time = networkMonitor.nowTime();

        GameWorld gameWorld = new GameWorld(time, mapData);
        snapshotManager.setInitialSnapshot(gameWorld, SNAPSHOT_MANAGER_INITIAL_POSITION);
        int nextUpdateTime = time + delay;
        System.out.println("Start modelling: " + time);
        
        //dummy cycle
        while(true) {
            time = networkMonitor.nowTime();
            if (time >= nextUpdateTime) {
                int dt = time - nextUpdateTime + delay;
                //System.out.println("NOW TIME: " + networkMonitor.nowTime() + "\n" + snapshotManager);
                snapshotManager.update(dt);
                nextUpdateTime += (dt / delay) * delay;

//                System.out.println(time + "; " + snapshotManager.getSnapshotByIndex(SNAPSHOT_MANAGER_INITIAL_POSITION).getTime());

                sendGameWorld();
            } else {
                try {
                    Thread.sleep(nextUpdateTime - time);
                } catch (InterruptedException ex) {
                    Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
        }
    }

    private boolean isFirst = true;
    private void sendGameWorld() throws Exception {
        int nowTime = networkMonitor.nowTime();
        for(IoSession session : previewGameManager.getReadySessions()) {
            Message message = new Message(session, GameWorldMessageDescription.getInstance());

            int ping = pingAnalyzer.getPing(session);
            if ( ping >= 0 ) {
                //System.out.println("SEND WITH PING = " + ping + "NOW TIME = " + nowTime);
                GameWorld snapshot = (GameWorld) snapshotManager.getSnapshot(nowTime + ping);
                //TODO: GreK: Почему +25?????
                //GameWorld snapshot = (GameWorld) snapshotManager.getSnapshot(nowTime + ping + 25);
                //System.out.println("SS TIME = " + snapshot.getTime());
                //System.out.println(nowTime + "; ping: " + ping + "; " + snapshot.getTime());

                if ( isFirst )
                {
                    snapshot = (GameWorld)snapshotManager.getSnapshotByIndex(SNAPSHOT_MANAGER_INITIAL_POSITION);

                    System.out.println("First Snapshot: " + nowTime + "; " + ping + "; " + snapshot.getTime());
                    isFirst = false;
                }

                message.putUserObject(snapshot);
                message.send();
            }
        }
    }

}